All files / web/src/app/api/curriculum/[playerId]/sessions/plans/[planId]/results/[resultIndex] route.ts

0% Statements 0/159
0% Branches 0/1
0% Functions 0/1
0% Lines 0/159

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160                                                                                                                                                                                                                                                                                                                               
import { NextResponse } from 'next/server'
import { withAuth } from '@/lib/auth/withAuth'
import { canPerformAction } from '@/lib/classroom'
import { getSessionPlan } from '@/lib/curriculum'
import { updateSessionPlanResults } from '@/lib/curriculum/session-planner'
import { getUserId } from '@/lib/viewer'

/**
 * PATCH /api/curriculum/[playerId]/sessions/plans/[planId]/results/[resultIndex]
 * Edit a specific result in the session plan
 *
 * Actions:
 * - mark_correct: Change an incorrect result to correct
 * - exclude: Mark result as excluded from tracking (source: 'teacher-excluded')
 * - include: Remove exclusion (restore original source)
 */
export const PATCH = withAuth(async (request, { params }) => {
  const {
    playerId,
    planId,
    resultIndex: resultIndexStr,
  } = (await params) as { playerId: string; planId: string; resultIndex: string }
  const resultIndex = parseInt(resultIndexStr, 10)

  if (isNaN(resultIndex) || resultIndex < 0) {
    return NextResponse.json({ error: 'Invalid result index' }, { status: 400 })
  }

  try {
    // Authorization: require 'start-session' permission (parent or teacher-present)
    const userId = await getUserId()
    const canModify = await canPerformAction(userId, playerId, 'start-session')
    if (!canModify) {
      return NextResponse.json({ error: 'Not authorized' }, { status: 403 })
    }

    const body = await request.json()
    const { action } = body

    // Get current plan
    const plan = await getSessionPlan(planId)
    if (!plan) {
      return NextResponse.json({ error: 'Plan not found' }, { status: 404 })
    }

    // Validate result index
    if (resultIndex >= plan.results.length) {
      return NextResponse.json({ error: 'Result index out of bounds' }, { status: 400 })
    }

    const result = plan.results[resultIndex]
    const updatedResults = [...plan.results]

    switch (action) {
      case 'mark_correct': {
        if (result.isCorrect) {
          return NextResponse.json({ error: 'Result is already correct' }, { status: 400 })
        }

        // Mark as correct and recalculate mastery weight
        const epochNumber = result.epochNumber ?? 0
        const masteryWeight = 1.0 / 2 ** epochNumber

        updatedResults[resultIndex] = {
          ...result,
          isCorrect: true,
          masteryWeight,
          // Add marker that this was teacher-corrected for audit trail
          source: 'teacher-corrected' as const,
        }

        // Handle retry queue implications
        // If this was epoch 0 and there are retry results for this slot, we should handle that
        // For now, we'll leave the retry results in place - they still happened
        // The BKT will recalculate based on the corrected result

        break
      }

      case 'exclude': {
        if (result.source === 'teacher-excluded') {
          return NextResponse.json({ error: 'Result is already excluded' }, { status: 400 })
        }

        // Store original source so we can restore it
        const originalSource = result.source

        updatedResults[resultIndex] = {
          ...result,
          source: 'teacher-excluded' as const,
          // Store original source in a new field for potential restoration
          _originalSource: originalSource,
        } as typeof result & { _originalSource?: string }

        break
      }

      case 'include': {
        if (result.source !== 'teacher-excluded') {
          return NextResponse.json({ error: 'Result is not excluded' }, { status: 400 })
        }

        // Restore original source
        const originalSource = (result as typeof result & { _originalSource?: string })
          ._originalSource

        updatedResults[resultIndex] = {
          ...result,
          source: (originalSource as typeof result.source) ?? 'practice',
        }

        // Remove the _originalSource field
        delete (
          updatedResults[resultIndex] as typeof result & {
            _originalSource?: string
          }
        )._originalSource

        break
      }

      default:
        return NextResponse.json(
          {
            error: 'Invalid action. Must be: mark_correct, exclude, or include',
          },
          { status: 400 }
        )
    }

    // Update the plan with modified results
    const updatedPlan = await updateSessionPlanResults(planId, updatedResults)

    return NextResponse.json({
      plan: {
        ...updatedPlan,
        createdAt:
          updatedPlan.createdAt instanceof Date
            ? updatedPlan.createdAt.getTime()
            : updatedPlan.createdAt,
        approvedAt:
          updatedPlan.approvedAt instanceof Date
            ? updatedPlan.approvedAt.getTime()
            : updatedPlan.approvedAt,
        startedAt:
          updatedPlan.startedAt instanceof Date
            ? updatedPlan.startedAt.getTime()
            : updatedPlan.startedAt,
        completedAt:
          updatedPlan.completedAt instanceof Date
            ? updatedPlan.completedAt.getTime()
            : updatedPlan.completedAt,
      },
    })
  } catch (error) {
    console.error('Error updating result:', error)
    return NextResponse.json({ error: 'Failed to update result' }, { status: 500 })
  }
})